#include <basic/errcode.h>
#include <basic/datetime.h>
#include <basic/dbc_in.h>
#include <basic/orac.h>

#include <oci.h>

#define INIT_ROWS               10
#define NEXT_ROWS               10

struct _orac_conn
{/*{{{*/
    OCIError *herr;
    OCISvcCtx *hsvc;
    OCIServer *hsvr;
    OCISession *haut;
};/*}}}*/

static struct _orac_conn orac_conn[DBC_CONN_MAX_NUM];
#define CONN orac_conn[conn_index]

static OCIEnv *henv;

static sword _check_err(int conn_index, sword status, sb4 *errcode)
{/*{{{*/
    text errbuf[BUFFER_SIZE/2];

    *errcode=ORA_OK; 
    switch(status)
    {
        case OCI_SUCCESS:
        case OCI_SUCCESS_WITH_INFO:
        case OCI_NEED_DATA:
        case OCI_STILL_EXECUTING:
        case OCI_CONTINUE:
            break;

        case OCI_NO_DATA:
            *errcode=ORA_NO_DATA_FOUND;
            break;

        case OCI_ERROR:
            OCIErrorGet((dvoid *)CONN.herr, (ub4)1, (text *)NULL,
                    errcode, errbuf, (ub4)sizeof(errbuf),
                    (ub4)OCI_HTYPE_ERROR);
            if(*errcode == 0)  *errcode=ORA_ERROR;
            break;

        case OCI_INVALID_HANDLE:
            *errcode=ORA_ERROR;
            break;

        default:
            break;
    }
    return(status);
}/*}}}*/

static void _free_handle(int conn_index)
{/*{{{*/
    if(CONN.haut)
    {
        OCIHandleFree((dvoid *)CONN.haut, OCI_HTYPE_SESSION);
        CONN.haut=(OCISession *)NULL;
    }
    if(CONN.hsvr)
    {
        OCIHandleFree((dvoid *)CONN.hsvr, OCI_HTYPE_SERVER);
        CONN.hsvr=(OCIServer *)NULL;
    }
    if(CONN.hsvc)
    {
        OCIHandleFree((dvoid *)CONN.hsvc, OCI_HTYPE_SVCCTX);
        CONN.hsvc=(OCISvcCtx *)NULL;
    }
    if(CONN.herr)
    {
        OCIHandleFree((dvoid *)CONN.herr, OCI_HTYPE_ERROR);
        CONN.herr=(OCIError *)NULL;
    }
}/*}}}*/

static void _free_bind(struct dbc_bind *bind, int bind_num)
{/*{{{*/
    if(bind)
    {
        int col_idx;

        for(col_idx=0; col_idx<bind_num; col_idx++)
            FREE(bind->vars[col_idx]);
    } 
}/*}}}*/

struct _col_buf
{/*{{{*/
    ub2 size;
    sb2 ind;
    char *value;
};/*}}}*/

static void _free_col_buf(struct _col_buf *col_buf, int col_num)
{/*{{{*/
    int col_idx;

    for(col_idx=0; col_idx<col_num; col_idx++)
        FREE(col_buf[col_idx].value);
}/*}}}*/

int orac_init()
{/*{{{*/
    sb4 status;
    int conn_index;

    henv=(OCIEnv *)NULL;
    status=OCIEnvCreate(&henv, 
            (ub4)(OCI_THREADED | OCI_OBJECT | OCI_SHARED),
            (dvoid *)NULL, (dvoid *(*)(dvoid *,size_t))NULL,
            (dvoid *(*)(dvoid *,dvoid *,size_t))NULL,
            (void (*)(dvoid *,dvoid *))NULL, (size_t)0, (dvoid **)NULL);
    if(status == OCI_ERROR)
    {
        henv=(OCIEnv *)NULL;
        return(-1);
    }
    for(conn_index=0; conn_index<DBC_CONN_MAX_NUM; conn_index++)
    {
        CONN.herr=(OCIError *)NULL;
        CONN.hsvc=(OCISvcCtx *)NULL;
        CONN.hsvr=(OCIServer *)NULL;
        CONN.haut=(OCISession *)NULL;
    }  

    return(0);
}/*}}}*/

void orac_clean()
{/*{{{*/
    OCIHandleFree((dvoid *)henv, OCI_HTYPE_ENV);
}/*}}}*/

int orac_connect_ex(int conn_index,
        char *dbuser, char *dbpasswd, char *host)
{/*{{{*/
    int dbuser_size, dbpasswd_size, host_size;
    sb4 status, errcode;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr != NULL)
        return(ORA_ERROR);
    if(dbuser == NULL || (dbuser_size=strlen(dbuser)) <= 0 ||
            dbuser_size > NAME_SIZE ||
        dbpasswd == NULL || (dbpasswd_size=strlen(dbpasswd)) <= 0 ||
        dbpasswd_size > NAME_SIZE)
        return(ORA_ERROR);
    if(!host || BUF_IS_CLR(host))
    {   host_size=0;   }
    else
    {
        host_size=strlen(host);
        if(host_size <= 0 || host_size > NAME_SIZE)
            return(ORA_ERROR);
    }

    // 分配err,svc,svr,aut,stmt
    if(
            (status=_check_err(conn_index,
                    OCIHandleAlloc((CONST dvoid *)henv,
                        (dvoid **)&CONN.herr,
                        (ub4)OCI_HTYPE_ERROR,
                        (size_t)0, (dvoid **)NULL),
                    &errcode)) == OCI_ERROR ||
            (status=_check_err(conn_index,
                               OCIHandleAlloc((CONST dvoid *)henv,
                                   (dvoid **)&CONN.hsvc,
                                   (ub4)OCI_HTYPE_SVCCTX,
                                   (size_t)0, (dvoid **)NULL),
                               &errcode)) == OCI_ERROR ||
            (status=_check_err(conn_index,
                               OCIHandleAlloc((CONST dvoid *)henv,
                                   (dvoid **)&CONN.hsvr,
                                   (ub4)OCI_HTYPE_SERVER,
                                   (size_t)0, (dvoid **)NULL),
                               &errcode)) == OCI_ERROR ||
            (status=_check_err(conn_index,
                               OCIHandleAlloc((CONST dvoid *)henv,
                                   (dvoid **)&CONN.haut,
                                   (ub4)OCI_HTYPE_SESSION,
                                   (size_t)0, (dvoid **)NULL),
                               &errcode)) == OCI_ERROR
            )
            {
                _free_handle(conn_index);
                return((int)errcode);
            }

    // 设置svr
    OCIAttrSet((dvoid *)CONN.hsvc, (ub4)OCI_HTYPE_SVCCTX,
            (dvoid *)CONN.hsvr, (ub4)0, (ub4)OCI_ATTR_SERVER,
            CONN.herr);
    // 设置aut
    OCIAttrSet((dvoid *)CONN.haut, (ub4)OCI_HTYPE_SESSION,
                    (dvoid *)dbuser, (ub4)dbuser_size,
                    (ub4)OCI_ATTR_USERNAME, CONN.herr); // dbuser
    OCIAttrSet((dvoid *)CONN.haut, (ub4)OCI_HTYPE_SESSION,
                    (dvoid *)dbpasswd, (ub4)dbpasswd_size,
                    (ub4)OCI_ATTR_PASSWORD, CONN.herr); // dbpasswd

    // 开始会话
    OCIServerAttach(CONN.hsvr, CONN.herr, (CONST text *)host,
            (sb4)host_size, (ub4)0);    // host
    if((status=_check_err(conn_index,
                    OCISessionBegin(CONN.hsvc, CONN.herr, CONN.haut,
                        OCI_CRED_RDBMS, (ub4)OCI_DEFAULT),
                    &errcode)) == OCI_ERROR)
    {
        _free_handle(conn_index);
        return((int)errcode);
    }
    OCIAttrSet((dvoid *)CONN.hsvc, (ub4)OCI_HTYPE_SVCCTX,
            (dvoid *)CONN.haut, (ub4)0, (ub4)OCI_ATTR_SESSION,
            CONN.herr);

    return(ORA_OK);
}/*}}}*/

int orac_disconnect_ex(int conn_index)
{/*{{{*/
    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL)
        return(ORA_ERROR);

    OCISessionEnd(CONN.hsvc, CONN.herr, CONN.haut, OCI_DEFAULT);
    OCIServerDetach(CONN.hsvr, CONN.herr, OCI_DEFAULT);
    _free_handle(conn_index);

    return(ORA_OK);
}/*}}}*/

int orac_begin_ex(int conn_index)
{/*{{{*/
    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL)
        return(ORA_ERROR);
    return(ORA_OK);
}/*}}}*/

int orac_commit_ex(int conn_index)
{/*{{{*/
    sb4 errcode;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL)
        return(ORA_ERROR);

    _check_err(conn_index,
            OCITransCommit(CONN.hsvc, CONN.herr, (ub4)OCI_DEFAULT),
            &errcode);
    return((int)errcode);
}/*}}}*/

int orac_rollback_ex(int conn_index)
{/*{{{*/
    sb4 errcode;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL)
        return(ORA_ERROR);

    _check_err(conn_index,
            OCITransRollback(CONN.hsvc, CONN.herr, (ub4)OCI_DEFAULT),
            &errcode);
    return((int)errcode);
}/*}}}*/

int orac_exec_ex(int conn_index, char *sql,
        DBC_BIND *bind, int bind_num)
{/*{{{*/
    sword status;
    sb4 errcode;
    OCIStmt *hstmt;
    ub4 pos;
    OCIBind *hbind[DBC_BIND_MAX_NUM];

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL)
        return(ORA_ERROR);

    if((status=_check_err(conn_index,
                    OCIHandleAlloc((CONST dvoid *)henv,
                        (dvoid **)&hstmt, OCI_HTYPE_STMT,
                        (size_t)0, (dvoid **)NULL),
                    &errcode)) == OCI_ERROR)
        return((int)errcode);
    if((status=_check_err(conn_index,
                    OCIStmtPrepare(hstmt, CONN.herr,
                        (text *)sql, (ub4)strlen(sql),
                        (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT),
                    &errcode)) == OCI_ERROR)
        goto ERROR_OUT;

    if(bind)
    {
        /*
        sb4 found;

        if((status=_check_err(conn_index,
                        OCIStmtGetBindInfo(hstmt, CONN.herr,
                            (ub4)0, (ub4)1, &found,
                            NULL, NULL,
                            NULL, NULL,
                            NULL, NULL),
                        &errcode)) == OCI_ERROR)
            goto ERROR_OUT;
        if(bind_num != found)
        {
            errcode=ORA_BIND_NUM_DISCORD;
            goto ERROR_OUT;
        }
        */
        if(bind_num < 0 || bind_num > DBC_BIND_MAX_NUM)
            goto ERROR_OUT;
        for(pos=1; pos<=bind_num; pos++)
        {
            if((status=_check_err(conn_index,
                            OCIBindByPos(hstmt, &hbind[pos-1],
                                CONN.herr, pos,
                                (dvoid *)bind->vars[pos-1],
                                (sb4)(strlen(bind->vars[pos-1])+1),
                                (ub2)SQLT_STR,
                                (dvoid *)NULL, (ub2 *)NULL,
                                (ub2 *)NULL, (ub4)0, (ub4 *)NULL,
                                (ub4)OCI_DEFAULT),
                            &errcode)) == OCI_ERROR)
                goto ERROR_OUT;
        }
    }

    if((status=_check_err(conn_index,
                    OCIStmtExecute(CONN.hsvc, hstmt, CONN.herr,
                    (ub4)1, (ub4)0,
                    (CONST OCISnapshot *)NULL, (OCISnapshot *)NULL,
                    OCI_DEFAULT),
                    &errcode)) == OCI_ERROR)
        goto ERROR_OUT;

    OCIHandleFree((dvoid *)hstmt, OCI_HTYPE_STMT);
    _free_bind(bind, bind_num);
    return(ORA_OK);

ERROR_OUT:
    OCIHandleFree((dvoid *)hstmt, OCI_HTYPE_STMT);
    _free_bind(bind, bind_num);
    return((int)errcode);
}/*}}}*/

int orac_query_ex(int conn_index, char *sql,
        DBC_BIND *bind, int bind_num,
        int request_row_num, DBC_RSET *rset)
{/*{{{*/
    sword status;
    sb4 errcode;
    OCIStmt *hstmt;
    OCIBind *hbind[DBC_BIND_MAX_NUM];
    OCIParam *hparam;
    ub4 pos;
    OCIDefine *hdefine[ORAC_DEFINE_MAX_NUM];
    struct _col_buf col_buf[ORAC_DEFINE_MAX_NUM];
    int col_num=0;
    ub2 col_type;
    int row_idx, col_idx;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL)
        return(ORA_ERROR);

    /*----- 初始化返回结果集 -----*/
    rset->rows=(char ***)NULL;
    rset->col_num=0;
    rset->row_num=0;
    rset->row_alloc_num=0;
    rset->query_flag=-1;

    /*----- 绑定项 -----*/
    if((status=_check_err(conn_index,
                    OCIHandleAlloc((CONST dvoid *)henv,
                        (dvoid **)&hstmt, OCI_HTYPE_STMT,
                        (size_t)0, (dvoid **)NULL),
                    &errcode)) == OCI_ERROR)
    {
        hstmt=(OCIStmt *)NULL;
        goto ERROR_BIND;
    }
    if((status=_check_err(conn_index,
                    OCIStmtPrepare(hstmt, CONN.herr,
                        (text *)sql, (ub4)strlen((char *)sql),
                        (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT),
                    &errcode)) == OCI_ERROR)
        goto ERROR_BIND;

    if(bind)
    {
        /*
        sb4 found;

        if((status=_check_err(conn_index,
                        OCIStmtGetBindInfo(hstmt, CONN.herr,
                            (ub4)0, (ub4)1, &found,
                            (text **)NULL, (ub1 *)NULL,
                            (text **)NULL, (ub1 *)NULL,
                            (ub1 *)NULL, (OCIBind **)NULL),
                        &errcode)) == OCI_ERROR)
            goto ERROR_BIND;
        if(bind_num != found)
        {
            errcode=ORA_BIND_NUM_DISCORD;
            goto ERROR_BIND;
        }
        */
        if(bind_num < 0 || bind_num > DBC_BIND_MAX_NUM)
            goto ERROR_BIND;
        for(pos=1; pos<=bind_num; pos++)
        {
            if((status=_check_err(conn_index,
                            OCIBindByPos(hstmt, &hbind[pos-1],
                                CONN.herr, (ub4)pos,
                                (dvoid *)bind->vars[pos-1],
                                (sb4)strlen(bind->vars[pos-1])+1,
                                SQLT_STR,
                                (dvoid *)NULL, (ub2 *)NULL,
                                (ub2 *)NULL, (ub4)0, (ub4 *)NULL,
                                OCI_DEFAULT),
                            &errcode)) == OCI_ERROR)
                goto ERROR_BIND;
        }
    }

    /*----- 选择项 -----*/
    for(col_idx=0; col_idx<ORAC_DEFINE_MAX_NUM; col_idx++)
        col_buf[col_idx].value=(char *)NULL;
    if((status=_check_err(conn_index,
                    OCIStmtExecute(CONN.hsvc, hstmt, CONN.herr,
                        (ub4)0, (ub4)0, (CONST OCISnapshot *)NULL,
                        (OCISnapshot *)NULL, OCI_DEFAULT),
                    &errcode)) == OCI_ERROR)
        goto ERROR_DEFINE;
    for(col_num=0;
            OCIParamGet((dvoid *)hstmt, OCI_HTYPE_STMT, CONN.herr,
                (dvoid **)&hparam, (ub4)(col_num+1)) == OCI_SUCCESS;
            col_num++)
    {
        // 类型
        if((status=_check_err(conn_index,
                        OCIAttrGet((dvoid *)hparam,
                            (ub4)OCI_DTYPE_PARAM,
                            (dvoid *)&col_type,
                            (ub4 *)NULL,
                            (ub4)OCI_ATTR_DATA_TYPE, CONN.herr),
                        &errcode)) == OCI_ERROR)
            goto ERROR_DEFINE;
        // 大小
        if((status=_check_err(conn_index,
                        OCIAttrGet((dvoid *)hparam,
                            (ub4)OCI_DTYPE_PARAM,
                            (dvoid *)&col_buf[col_num].size,
                            (ub4 *)NULL,
                            (ub4)OCI_ATTR_DATA_SIZE, CONN.herr),
                        &errcode)) == OCI_ERROR)
            goto ERROR_DEFINE;

        switch(col_type)
        {
            case SQLT_NUM:
                col_buf[col_num].size=50;
                break;

            case SQLT_LNG:
            case SQLT_LBI:
                col_buf[col_num].size=240;
                break;

            case SQLT_RID:
            case SQLT_RDD:
                col_buf[col_num].size=18;
                break;

            case SQLT_DAT:
                col_buf[col_num].size=TIMESTAMP_SIZE;
                break;
        }
        col_buf[col_num].value=(char *)malloc(
                col_buf[col_num].size+1);
        //if(col_type != SQLT_LBI)  col_type=SQLT_CHAR;
        if((status=_check_err(conn_index,
                        OCIDefineByPos(hstmt, &hdefine[col_num],
                            CONN.herr, (ub4)(col_num+1),
                            (dvoid *)col_buf[col_num].value,
                            (sb4)(col_buf[col_num].size+1),
                            SQLT_STR,
                            (dvoid *)&col_buf[col_num].ind,
                            (ub2 *)NULL, (ub2 *)NULL, OCI_DEFAULT),
                       &errcode)) == OCI_ERROR)
           goto ERROR_DEFINE;
    }
    rset->col_num=col_num;

    while(1)
    {
        status=_check_err(conn_index,
                OCIStmtFetch2(hstmt, CONN.herr,
                    (ub4)1, (ub2)OCI_DEFAULT,
                    (sb4)0, (ub4)OCI_DEFAULT),
                &errcode);
        if(status != OCI_SUCCESS && status != OCI_NO_DATA)
            goto ERROR_DEFINE;
        if(status == OCI_NO_DATA)  break;
        if(request_row_num > 0 &&
                rset->row_num == request_row_num+1)
            break;

        /* 扩展行 */
        if(rset->row_num == rset->row_alloc_num)
        {
            char ***ptr;
            if(!(ptr=(char ***)realloc(rset->rows,
                   (rset->row_num+NEXT_ROWS)*sizeof(char **))))
            {
                errcode=ORA_ERROR;
                goto ERROR_DEFINE;
            }
            rset->rows=ptr;
            for(row_idx=rset->row_num; row_idx<NEXT_ROWS;
                row_idx++)
                rset->rows[row_idx]=NULL;
            rset->row_alloc_num+=NEXT_ROWS;
        }
        /* 行 */
        row_idx=rset->row_num;
        errcode=ORA_ERROR;
        ALLOC_MULTI(rset->rows[row_idx], char *, col_num, ERROR_DEFINE);
        for(col_idx=0; col_idx<col_num; col_idx++)
        {
            /* 列 */
            ALLOC_MULTI(rset->rows[row_idx][col_idx], char,
                    (int)col_buf[col_idx].size+8,
                    ERROR_DEFINE);
            if(col_buf[col_idx].ind < 0)  /* NULL */
                sprintf(rset->rows[row_idx][col_idx], "%-*c",
                    (int)col_buf[col_idx].size, SPACE);
            else
                sprintf(rset->rows[row_idx][col_idx], "%-*.*s",
                    (int)col_buf[col_idx].size,
                    (int)col_buf[col_idx].size,
                    col_buf[col_idx].value);
        }
        rset->row_num++;
    }

    /*----- 结束标志 -----*/
	if(rset->row_num == 0)
    {
        errcode=ORA_NO_DATA_FOUND;
        goto ERROR_DEFINE;
    }
    if(request_row_num>0 && rset->row_num == request_row_num+1)
    {
        rset->query_flag=QUERY_HALFWAY;
        rset->row_num=request_row_num;
    }
    else
        rset->query_flag=QUERY_BOTTOM;

    _free_col_buf(col_buf, col_num);
    OCIHandleFree((dvoid *)hstmt, OCI_HTYPE_STMT);
    _free_bind(bind, bind_num);
    return(ORA_OK);

ERROR_DEFINE:
    dbc_query_free(rset);
    _free_col_buf(col_buf, col_num);
ERROR_BIND:
    OCIHandleFree((dvoid *)hstmt, OCI_HTYPE_STMT);
    _free_bind(bind, bind_num);
    return((int)errcode);
}/*}}}*/

int orac_sequence_nextval_ex(int conn_index, char *seq, long *nextval)
{/*{{{*/
    char sql[200];
    int result;
    DBC_RSET rset;

    if(conn_index < 0 || conn_index > DBC_CONN_MAX_NUM-1 ||
            CONN.herr == NULL ||
            !seq || !nextval)
        return(ORA_ERROR);

    sprintf(sql, "select %s.nextval from dual", seq);
    result=orac_query_ex(conn_index, sql, NULL, -1, 1, &rset);
    if(result != ORA_OK)  return(result);
    if(rset.row_num == 0)  return(ORA_ERROR);
    trim_tail_space_notnull(rset.rows[0][0]);
    *nextval=atol(rset.rows[0][0]);
    dbc_query_free(&rset);

    return(ORA_OK); 
}/*}}}*/
